/*
* Copyright (c) 2014 Intellectual Reserve, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cf.spring;
import cf.component.BasicVarzProducer;
import cf.component.VarzAggregator;
import cf.component.VarzProducer;
import cf.component.util.DateTimeUtils;
import cf.nats.CfNats;
import cf.nats.DefaultCfNats;
import cf.nats.Publication;
import cf.nats.PublicationHandler;
import cf.nats.message.ComponentAnnounce;
import cf.nats.message.ComponentDiscover;
import nats.client.Nats;
import nats.client.Subscription;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.lang.management.ManagementFactory;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author Mike Heath
*/
@Configuration
public class CfComponentConfiguration implements InitializingBean, DisposableBean, ImportAware, BeanFactoryAware {
@Autowired
private Nats nats;
@Autowired(required = false)
private CfNats cfNats;
private BeanExpressionResolver expressionResolver;
private BeanExpressionContext expressionContext;
private String type;
private int index;
private String uuid;
private String host;
private int port;
private String username;
private String password;
private Subscription componentDiscoverSubscription;
private final long startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
final MultiValueMap<String,Object> attributes = importMetadata.getAllAnnotationAttributes(CfComponent.class.getName());
type = evaluate(attributes, "type");
index = Integer.valueOf(evaluate(attributes, "index"));
uuid = evaluate(attributes, "uuid");
if (StringUtils.isEmpty(uuid)) {
uuid = UUID.randomUUID().toString();
}
host = evaluate(attributes, "host");
port = Integer.valueOf(evaluate(attributes, "port"));
username = evaluate(attributes, "username");
password = evaluate(attributes, "password");
if (StringUtils.isEmpty(password)) {
password = new BigInteger(256, ThreadLocalRandom.current()).toString(32);
}
}
private String evaluate(MultiValueMap<String,Object> annotationAttributes, String attribute) {
final String expression = annotationAttributes.getFirst(attribute).toString();
final Object value = expressionResolver.evaluate(expression, expressionContext);
return value == null ? null : value.toString();
}
@Override
public void afterPropertiesSet() throws Exception {
if (cfNats == null) {
cfNats = new DefaultCfNats(nats);
}
componentDiscoverSubscription = cfNats.subscribe(ComponentDiscover.class, new PublicationHandler<ComponentDiscover, ComponentAnnounce>() {
@Override
public void onMessage(Publication<ComponentDiscover, ComponentAnnounce> publication) {
publication.reply(buildComponentAnnounceMessage());
}
});
cfNats.publish(buildComponentAnnounceMessage());
}
@Override
public void destroy() throws Exception {
componentDiscoverSubscription.close();
}
@Bean
HealthzHandlerMapping healthzHandlerMapping() {
return new HealthzHandlerMapping();
}
@Bean
VarzHandlerMapping varzHandlerMapping(List<VarzProducer> varzProducers) {
return new VarzHandlerMapping(new VarzAggregator(varzProducers), new HttpBasicAuthenticator("", username, password));
}
@Bean
BasicVarzProducer basicVarz() {
return new BasicVarzProducer(type, index, uuid);
}
public String getType() {
return type;
}
public int getIndex() {
return index;
}
public String getUuid() {
return uuid;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
private ComponentAnnounce buildComponentAnnounceMessage() {
return new ComponentAnnounce(
type,
index,
uuid,
host + ":" + port,
Arrays.asList(username, password),
DateTimeUtils.formatDateTime(startTime),
DateTimeUtils.formatUptime(startTime)
);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
final ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
expressionResolver = cbf.getBeanExpressionResolver();
expressionContext = new BeanExpressionContext(cbf, cbf.getRegisteredScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE));
} else {
throw new BeanCreationException(getClass().getName() + " can only be used with a " + ConfigurableBeanFactory.class.getName());
}
}
}